package sicnu.cs.ich.security.security.jwt;


import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import sicnu.cs.ich.security.config.AppProperties;
import sicnu.cs.ich.security.util.CollectionUtil;
import sicnu.cs.ich.security.util.JwtUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author CaiKe
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class JwtFilter extends OncePerRequestFilter {

    private final AppProperties appProperties;
    private final JwtUtil jwtUtil;
    private final ObjectMapper objectMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        var claimsOpt = Optional.ofNullable((Claims) null);
        if (checkJwtToken(request)) {
            claimsOpt = validateToken(request);
            claimsOpt.filter(claims -> Objects.nonNull(claims.get("authorities")))
                    .ifPresentOrElse(
                            this::setSpringAuthentication,
                            SecurityContextHolder::clearContext
                    );
        }
        //if (claimsOpt.isPresent()) {
        //    val un = claimsOpt.get().getSubject();
        //    val userOpt = userMapper.findOptionalByUsername(un);
        //    userOpt.ifPresent(user -> request.setAttribute("user", user));
        //    if (userOpt.isEmpty()) {
        //        writeStatusInfo(response);
        //        return;
        //    }
        //}
        filterChain.doFilter(request, response);
    }

    private void writeStatusInfo(HttpServletResponse response) {
        var map = Map.of("status", false, "info", "用户未激活");
        try {
            var str = objectMapper.writeValueAsString(map);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setStatus(HttpStatus.LOCKED.value());
            response.getWriter().print(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean checkJwtToken(HttpServletRequest request) {
        var header = request.getHeader(appProperties.getJwt().getHeader());
        return Strings.isNotEmpty(header) && header.startsWith(appProperties.getJwt().getPrefix());
    }

    /**
     * 获取 Token 中的Claims中保存的信息，无则返回 empty()
     */
    private Optional<Claims> validateToken(HttpServletRequest request) {
        var token = request.getHeader(appProperties.getJwt().getHeader()).replace(appProperties.getJwt().getPrefix(), "");
        try {
            return Optional.of(Jwts.parserBuilder().setSigningKey(jwtUtil.getKey()).build().parseClaimsJws(token).getBody());
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    private void setSpringAuthentication(Claims claims) {
        var rawList = CollectionUtil.convertObjectToList(claims.get("authorities"));
        var authorities = rawList.stream()
                .map(String::valueOf)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        var authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
